/*
 * Copyright (C) 2010 Charles Norona, Allan Pinero, Christopher Sizelove
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package edu.fau.rifl;

/**
 *
 * @author allan
 */

import java.awt.Image;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.NoSuchElementException;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.lang.NumberFormatException;

import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.UUID;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;


/**
 * A class that demonstrates Bluetooth communication between server mode PC and
 * client mode device through serial port profile.
 *
 * @see <a href="http://sourceforge.net/projects/bluecove/">BlueCove - JSR-82
 *      implementation</a>
 */
public class BluetoothRFCOMM {

	/*-
	 * ================
	 * Bluetooth Server
	 * ================
	 *
	 * This example application is a straighforward implementation of
	 * a bluetooth server.
	 *
	 *
	 * Usage
	 * =====
	 *
	 * Start the program. Events are logged by printing them out with standard
	 * output stream. Once the server is up and ready to accept connections,
	 * connect to server with client.
	 *
	 *
	 * How it Works
	 * ============
	 *
	 * The application starts a loop to wait for a new connection. After a new
	 * connection is reseived the connection is handled. In the handling
	 * operation few tokens and end token is written to connection stream.
	 * Each read token is logged to standard output. After handling session
	 * the loop continues by waiting for a new connection.
	 *
	 */

	/*-
	 *
	 * ---- Bluetooth attributes ----
	 */

	// serial port profile
	protected String UUID = new UUID("0000101100001000800000805f9b34fb", false).toString();
	protected int discoveryMode = DiscoveryAgent.GIAC; // no pairing needed
	//protected int discoveryMode = DiscoveryAgent.LIAC; 

	public static InputStream in;
	public static OutputStream out;
	private static boolean ServerRun = true;
	protected int endToken = 255;

	//Bluetooth and Sensor variables
	protected double newTimeStamp = 0;
	protected double oldTimeStamp = 0;
	protected String sensorName = "";
	protected String sensorType = "";
	protected String sensorVendor = "";
	protected String sensorVersion = "";
	protected String maxRange = "";
	protected String powerDissipation = "";
	protected String resolution = "";
	protected String accuracy = "";
	protected String timeStamp = "";
	protected String[] values = new String[3];
	protected boolean isTransmitting = false;

	//Base station variables
	private String[] accValues = new String[3];
	private String[] oriValues = new String[3];
	protected double timeChange = 0;
	protected double magnitude = 0;
	protected double distanceChange = 0;
	protected String calibratedValue = "";
	protected double bearing = 0;
	protected String comparator = "";

	// File IO
	private int numberOfRuns = 0;
	private FileWriter fWriter;
	//File filepath = new File("C:\\Documents and Settings\\cnorona1\\Desktop");
	private File filepath = new File("C:\\RIFL");
	private PrintWriter pWriter;
	private String fileOutMsg = "";

	public BluetoothRFCOMM() 
	{
		ServerRun = true;

		fileSetup(); 

		try {
			LocalDevice device = LocalDevice.getLocalDevice();
			device.setDiscoverable(DiscoveryAgent.GIAC);

			String url = "btspp://localhost:" + UUID + ";name=PCServerCOMM";

			log("Create server by uri: " + url);
			StreamConnectionNotifier notifier = (StreamConnectionNotifier) Connector.open(url);

			serverLoop(notifier);

		} catch (Throwable e) {
			log(e);
		}
	}

	/**
	 * Main algorithm function. Used for making console applications or testing this class.
	 * @param args
	 * @throws IOException
	 * @throws InterruptedException
	 */
	//	public static void main(String[] args) throws IOException, InterruptedException
	//	{
	//		//Main algorithm, use this area to test this class.
	//		PCServerCOMM pCServerCOMM = new PCServerCOMM();
	//	}

	private void serverLoop(final StreamConnectionNotifier notifier) 
	{
		Thread handler = new Thread() 
		{
			@Override
			public void run() 
			{
				try {
					while (ServerRun)// infinite loop to accept connections. 
					{ 
						log("Waiting for connection...");
						handleConnection(notifier.acceptAndOpen());
					}
				} catch (Exception e) {
					log(e);
				}
				try 
				{
					Thread.sleep(200);
				} 
				catch (InterruptedException ex) 
				{
					Logger.getLogger(BluetoothRFCOMM.class.getName()).log(Level.SEVERE, null, ex);
				}
			}
		};
		handler.start();
	}

	/**
	 * Establishes RFCOMM datastream connection.
	 *
	 * @param conn
	 * 		This interface defines the capabilities that a stream connection must have.
	 * @exception IOException
	 * 		Signals that an I/O exception of some sort has occurred.
	 * 		This class is the general class of exceptions produced by failed or interrupted I/O operations.
	 *
	 */
	private void handleConnection(StreamConnection conn) throws IOException {
		out = conn.openOutputStream();
		in = conn.openInputStream();
		startReadThread(in);
		log("Connection found...");
	}

	/**
	 * This is the thread that reads the datastreams from the bluetooth radio.
	 *
	 * @param in
	 * 		This abstract class is the superclass of all classes representing an input stream of bytes.
	 *
	 */
	private void startReadThread(final InputStream in)
	{
		Thread reader = new Thread() 
		{
			byte[] byteBuffer = new byte[2048];//Buffer.
			String readMessage = new String(byteBuffer);
			int storeByte;

			@Override
			public void run()
			{
				try
				{
					// String stringImage = Base64.encode(imag);
					log("Start echo loop.");

					for (;storeByte<1024;)
					{
						storeByte=in.read(byteBuffer);
						readMessage = new String(byteBuffer,0,storeByte);
						isTransmitting = true;

						try
						{
							if(parseBTMessage(readMessage))//Parse message information
							{
								comparator = readMessage.substring(0, 6);

								if(comparator.equals("BMA150"))//Compare to model number of sensor. Will need to change this for scalablity.
								{
									try
									{
										magnitude = Math.abs(determineMagnitude(values[0],values[1],values[2]) - Double.valueOf(calibratedValue));//Determine magnitude
									}
									catch (java.lang.NumberFormatException e)//Catches orientation parse errors
									{
										log(e);
									}
								}
								else if(comparator.equals("AK8973"))
								{
									bearing = Double.valueOf(values[0]);
								}

								timeChange = determineTimeChange(timeStamp);//Determine change in time
								distanceChange = determineDistanceChange(magnitude, timeChange);//Determine change in distance

								fileOutMsg = sensorName  
								+ "\t" + sensorType 
								+ "\t" + sensorVendor 
								+ "\t" + sensorVersion
								+ "\t" + maxRange 
								+ "\t" + powerDissipation 
								+ "\t" + resolution 
								+ "\t" + accuracy
								+ "\t" + timeStamp 
								+ "\t" + values[0]
								                + "\t" + values[1] 
								                                + "\t" + values[2] 
								                                                + "\t" + Double.toString(magnitude) 
								                                                + "\t" + Double.toString(timeChange) 
								                                                + "\t" + Double.toString(distanceChange)
								                                                + "\t" + calibratedValue;

								pWriter.println(fileOutMsg);
							}
						}
						catch(NullPointerException e)
						{
							System.out.println("Bluetooth message reading did not work!");
						}

						System.out.println(readMessage);//Debug output
					}
				}
				catch (IOException e)
				{
					log(e);
				}
				finally
				{
					log("Bluetooth stream closed.");
				}
			}            
		};
		reader.start();
	}

	/*-
	 *   -------  Utility section -------
	 */
	private void log(String msg) 
	{
		//MOBILEOR_GUI.AppendTostatus(msg + "\n");
		System.out.println(msg);
	}

	/**
	 * This function is called in the catch clauses of all try-catch blocks to handle exceptions.
	 *
	 * @param e
	 * 		Exception code.
	 */
	private void log(Throwable e) 
	{
		log(e.getMessage());

		e.printStackTrace();
	}

	/**
	 * This function is called when the bluetooth server needs to stop advertising
	 * connection invitations.
	 */
	public  void StopServer()
	{
		ServerRun = false;

	}

	/**
	 * With given time and magnitude the distance is calculated. 
	 * NOTE: Meant to be used by base station application.
	 * @param magnitude, timeChange
	 */
	private double determineDistanceChange(Double mMagnitude, double mTimeChange)
	{
		distanceChange = 0;

		distanceChange = (double) (mMagnitude * (mTimeChange * mTimeChange));

		System.out.println("Magnitude: "+ mMagnitude 
				+ " Time Change: " + mTimeChange 
				+ " Distance Change: " + distanceChange);

		return distanceChange;
	}

	/**
	 * Takes the sensor values of the accelerometer and determines the magnitude using
	 *  those values. For the mobile device, it is used for calibrating the sensors.
	 * NOTE: Meant to be used by base station application.
	 * @param x,y,z
	 */
	private double determineMagnitude(String x, String y, String z)
	{
		double newX = 0, newY = 0, newZ = 0;
		double sum;
		double result;

		//Convert strings to double
		newX = Double.valueOf(x);
		newY = Double.valueOf(y);
		newZ = (Double.valueOf(z));

		//Squaring each value
		newX = newX * newX;
		newY = newY * newY;
		newZ = newZ * newZ;

		//Add them together
		sum = (newX + newY + newZ);

		//Take the square root
		result = Math.abs(Math.sqrt(sum));

		//Make the magnitude positive

		return(result);
	}

	/**
	 * Calculates the difference of the respective remote device's previous timestamp with the current timestamp. 
	 * NOTE: Meant to be used by base station application.
	 * @param newTime
	 */
	private double determineTimeChange(String newTime)
	{
		double timeChange = 0;
		double mNewTime = 0;

		//Convert to double
		mNewTime = Double.valueOf(newTime);
		mNewTime = (mNewTime/1000000000);

		newTimeStamp = mNewTime;

		timeChange = newTimeStamp - oldTimeStamp;

		//Store old time stamp.
		oldTimeStamp = newTimeStamp;

		System.out.println("Old Timestamp: "+ oldTimeStamp 
				+ " New Timestamp: " + newTimeStamp 
				+ " Time Change: " + timeChange);

		return timeChange;
	}

	/**
	 * Sets up the file stream for writing.
	 * NOTE: Meant to be used by base station application.
	 */
	private void fileSetup() 
	{
		try 
		{
			if (!filepath.exists()) 
			{
				filepath.mkdirs();
			}

			fWriter = new FileWriter(filepath.getPath() + "/RIFL_ClientResults_" + System.currentTimeMillis() + ".txt");
			pWriter = new PrintWriter(fWriter);

		} 
		catch (Exception e) 
		{
			e.printStackTrace();
		}
		pWriter.println("Name: \t " +
				"Type: \t " +
				"Vendor: \t " +
				"Version: \t " +
				"Max Range: \t " +
				"Power: \t" +
				"Resolution: \t" +
				"Accuracy: \t" +
				"Timestamp: \t" +
				"Value[0]: \t" +
				"Value[1]: \t" +
				"Value[2]: \t" +
				"Magnitude: \t" +
				"Time Change: \t" +
				"Distance Change: \t" +
		"Calibrated Mag:");
	}

	/**
	 * Takes message and extracts information from it and stores values in respective
	 * data types. Also, outputs the values to a local file on the SD card. For the
	 * mobile device it is used for the calibration process.
	 * NOTE: Meant to be used by base station application.
	 * @param message
	 * 		String that is passed from message handler that contains the contents of the bluetooth transmission.
	 */
	private boolean parseBTMessage(String message)
	{
		if(message != null)
		{
			Scanner scanner = new Scanner(message);
			scanner.useDelimiter("\t");
			if ( scanner.hasNext() )
			{
				try 
				{
					sensorName = scanner.next();
					sensorType = scanner.next();
					sensorVendor = scanner.next();
					sensorVersion = scanner.next();
					maxRange = scanner.next();
					powerDissipation = scanner.next();
					resolution = scanner.next();
					accuracy = scanner.next();
					timeStamp = scanner.next();
					values[0] = scanner.next();
					values[1] = scanner.next();
					values[2] = scanner.next();
					calibratedValue = scanner.next();
				} 
				catch (NoSuchElementException e) 
				{
					e.printStackTrace();
					return false;
				}

				System.out.println("Name: "+ sensorName.trim() 
						+ ", Type: " + sensorType 
						+ ", Vendor: " + sensorVendor
						+ ", Version: " + sensorVersion
						+ ", Max Range: " + maxRange
						+ ", Power: " + powerDissipation
						+ ", Resolution: " + resolution
						+ ", accuracy: " + accuracy
						+ ", Timestamp: " + timeStamp 
						+ ", Values[0]: " + values[0]
						                           + ", Values[1]: " + values[1] 
						                                                      + ", Values[2]: " + values[2]
						                                                                                 + ", Calibrated Magnitude: " + calibratedValue);
			}
			else 
			{
				System.out.println("Sensor parser: Empty or invalid line. Unable to process.");
			}

			scanner.close();
		}

		return true;
	}
}


/*-
 *   -------  Bootstrap section, i.e., main method -------
 */
